library(tidyverse)
library(sf)
library(mapview)

1 Intro

This Rmarkdown notebook aims to show some examples of how to solve the classroom proposed exercises in QGIS, but in R.

There are several ways to reach the same solution. Here we present only one of them.

2 Represent Transport Zones

Download and open TRIPSgeo_mun.gpkg and TRIPSgeo_freg.gpkg under MQAT/geo/ repository.

TRIPSgeo_mun = st_read("geo/TRIPSgeo_mun.gpkg", quiet = TRUE) # we add quiet = TRUE so we don't get annoying messages on the info
TRIPSgeo_freg = st_read("geo/TRIPSgeo_freg.gpkg", quiet = TRUE)

# you can also open directly from url from github. example:
# TRIPSgeo_mun = st_read("https://github.com/U-Shift/MQAT/raw/main/geo/TRIPSgeo_mun.gpkg")

Represent Transport Zones with Total, and with Car %.

# create Car_per variable
TRIPSgeo_mun = TRIPSgeo_mun |> mutate(Car_per = Car / Total * 100)
TRIPSgeo_freg = TRIPSgeo_freg |> mutate(Car_per = Car / Total * 100)

#Vizualize in map
mapview(TRIPSgeo_mun, zcol = "Car_per")
mapview(TRIPSgeo_freg, zcol = "Car_per", col.regions = rev(hcl.colors(9, "-Inferno"))) #palete inferno com 9 classes, reverse color ramp

3 Centroids

3.1 Geometric centroids

CENTROIDSgeo = st_centroid(TRIPSgeo_mun)
# mapview(CENTROIDSgeo)

3.2 Weigthed centroids

Get BGRI Data1 from INE website, at Área Metropolitana de Lisboa level: https://mapas.ine.pt/download/index2021.phtml

BGRI = st_read("original/BGRI21_LISBOA.gpkg", quiet = TRUE)

It is not that easy to estimate weighted centroids with R. See here.
We will make a bridge connection to QGIS to use its native function of mean coordinates.

library(qgisprocess)
# qgis_search_algorithms("mean") # search the exact function name
# qgis_get_argument_specs("native:meancoordinates") |> select(name, description) # see the required inputs

# with population
CENTROIDSpop = qgis_run_algorithm(algorithm = "native:meancoordinates",
                                  INPUT = BGRI,
                                  WEIGHT = "N_INDIVIDUOS",
                                  UID = "DTMN21")
CENTROIDSpop = st_as_sf(CENTROIDSpop)

# with buildings
CENTROIDSbuild = qgis_run_algorithm(algorithm = "native:meancoordinates",
                                  INPUT = BGRI,
                                  WEIGHT = "N_EDIFICIOS_CLASSICOS",
                                  UID = "DTMN21")
CENTROIDSbuild = st_as_sf(CENTROIDSbuild)

3.3 Compare in map

mapview(CENTROIDSgeo) + mapview(CENTROIDSpop, col.region = "red") + mapview(CENTROIDSbuild, col.region = "black")

See how the building, poulation and geometric centroids of Montijo are appart, from closer to Tagus, to the rural area.

4 Desire Lines

Download TRIPSdl_mun.gpkg

TRIPSdl_mun = st_read("geo/TRIPSdl_mun.gpkg", quiet = TRUE) 

Filter intrazonal trips, and trips with origin or desination in Lisbon.

TRIPSdl_mun = TRIPSdl_mun |> 
  filter(Origin_mun != Destination_mun) |> 
  filter(Total > 5000) # remove noise

mapview(TRIPSdl_mun, zcol = "Total", lwd = 5)
TRIPSdl_mun_noLX = TRIPSdl_mun |> 
  filter(Origin_mun != "Lisboa", Destination_mun != "Lisboa")

mapview(TRIPSdl_mun_noLX, zcol = "Total", lwd = 8)

You can replace the Total with other variable, such as Car, PTransit, and so on.

Note: The function od_oneway() aggregates oneway lines to produce bidirectional flows. By default, it returns the sum of each numeric column for each bidirectional origin-destination pair. This is better for viz purpouses.

5 Euclidean vs. Routing distance

5.1 Euclidean distance

5.1.1 Create new point at IST

IST = st_sfc(st_point(c(-9.1397404, 38.7370168)), crs = 4326)

5.1.2 Import survey and visualize

SURVEY = read.csv("geo/SURVEYist.txt", sep = "\t") # tab delimiter
SURVEY = st_as_sf(SURVEY, coords = c("lon", "lat"), crs = 4326) # transform as geo data

mapview(SURVEY, zcol = "MODE") + mapview(IST, col.region = "red", cex = 12)

5.1.3 Reproject layers

In R we can process distances in meters on-fly.

Buy here is the code to project layers from Geographic coordinates (WGS 84 - EPSG:4364) to Projected coordinates (Pseudo-Mercator - EPSG:3857, or Portuguese Tranversor-Mercator 06 - EPSG:3763).

ISTprojected = st_transform(IST, crs = 3857)
SURVEYprojected = st_transform(SURVEY, crs = 3857)

5.1.4 Straight lines and distance

Nearest point between the two layers. As we only have 1 point at IST layer, we will have the same number of lines as number of surveys = 200.

SURVEYeuclidean = st_nearest_points(SURVEY, IST, pairwise = TRUE) |> st_as_sf() # this creates lines

mapview(SURVEYeuclidean)
SURVEY$distance = st_length(SURVEYeuclidean) # compute distance and add directly in the first layer

summary(SURVEY$distance) # in meters
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   298.1  1105.9  2185.5  2658.5  3683.4  8600.0

The same function can be used to find the closest GIRA station to each survey home location. And also check where are the ones that are far away from GIRA.

GIRA = st_read("geo/GIRA2023.geojson", quiet = TRUE) # we can also read geojson with this function!

nearest = st_nearest_feature(SURVEY, GIRA) # creates an index of the closest GIRA station id

SURVEY$distanceGIRA = st_distance(SURVEY, GIRA[nearest,], by_element = TRUE)

mapview(SURVEY, zcol = "distanceGIRA") +
  mapview(GIRA, col.regions = "grey20", cex = 4, legend = FALSE)

5.2 Routing distance

Using an API key from OpenRouteService, you should store it at your computer and never show it directly on code.
For that usethis::edit_r_environ() and paste your token as ORS_API_KEY="xxxxxxxxxxxxxxxxxxxxxx" (replace with your token). Save the .Renviron file, and press Ctrl+Shift+F10 to restart R so it can take effect.

Work In Progress


  1. Base Geográfica de Referenciação de Informação, Censos 2021↩︎

LS0tCnRpdGxlOiAiR0lTIGluIFIgLSBleGVyY2lzZXMiCmF1dGhvcjogIlIgRsOpbGl4IgpkYXRlOiAiTVFBVCAyMDIzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgIyBjb2RlX2ZvbGRpbmc6ICJoaWRlIgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UpCmBgYAoKYGBge3IgbGlicmFyaWVzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzZikKbGlicmFyeShtYXB2aWV3KQpgYGAKCgojIEludHJvCgpUaGlzIFJtYXJrZG93biBub3RlYm9vayBhaW1zIHRvIHNob3cgc29tZSBleGFtcGxlcyBvZiBob3cgdG8gc29sdmUgdGhlIGNsYXNzcm9vbSBwcm9wb3NlZCBleGVyY2lzZXMgaW4gUUdJUywgYnV0IGluIFIuCgpUaGVyZSBhcmUgc2V2ZXJhbCB3YXlzIHRvIHJlYWNoIHRoZSBzYW1lIHNvbHV0aW9uLiBIZXJlIHdlIHByZXNlbnQgb25seSBvbmUgb2YgdGhlbS4KCiMgUmVwcmVzZW50IFRyYW5zcG9ydCBab25lcwoKRG93bmxvYWQgYW5kIG9wZW4gYFRSSVBTZ2VvX211bi5ncGtnYCBhbmQgYFRSSVBTZ2VvX2ZyZWcuZ3BrZ2AgdW5kZXIgW01RQVQvZ2VvL10oaHR0cHM6Ly9naXRodWIuY29tL1UtU2hpZnQvTVFBVC90cmVlL21haW4vZ2VvKSByZXBvc2l0b3J5LgoKYGBge3IgZ2V0ZGF0YTF9ClRSSVBTZ2VvX211biA9IHN0X3JlYWQoImdlby9UUklQU2dlb19tdW4uZ3BrZyIsIHF1aWV0ID0gVFJVRSkgIyB3ZSBhZGQgcXVpZXQgPSBUUlVFIHNvIHdlIGRvbid0IGdldCBhbm5veWluZyBtZXNzYWdlcyBvbiB0aGUgaW5mbwpUUklQU2dlb19mcmVnID0gc3RfcmVhZCgiZ2VvL1RSSVBTZ2VvX2ZyZWcuZ3BrZyIsIHF1aWV0ID0gVFJVRSkKCiMgeW91IGNhbiBhbHNvIG9wZW4gZGlyZWN0bHkgZnJvbSB1cmwgZnJvbSBnaXRodWIuIGV4YW1wbGU6CiMgVFJJUFNnZW9fbXVuID0gc3RfcmVhZCgiaHR0cHM6Ly9naXRodWIuY29tL1UtU2hpZnQvTVFBVC9yYXcvbWFpbi9nZW8vVFJJUFNnZW9fbXVuLmdwa2ciKQpgYGAKClJlcHJlc2VudCBUcmFuc3BvcnQgWm9uZXMgd2l0aCBUb3RhbCwgYW5kIHdpdGggQ2FyICUuCgpgYGB7cn0KIyBjcmVhdGUgQ2FyX3BlciB2YXJpYWJsZQpUUklQU2dlb19tdW4gPSBUUklQU2dlb19tdW4gfD4gbXV0YXRlKENhcl9wZXIgPSBDYXIgLyBUb3RhbCAqIDEwMCkKVFJJUFNnZW9fZnJlZyA9IFRSSVBTZ2VvX2ZyZWcgfD4gbXV0YXRlKENhcl9wZXIgPSBDYXIgLyBUb3RhbCAqIDEwMCkKCiNWaXp1YWxpemUgaW4gbWFwCm1hcHZpZXcoVFJJUFNnZW9fbXVuLCB6Y29sID0gIkNhcl9wZXIiKQptYXB2aWV3KFRSSVBTZ2VvX2ZyZWcsIHpjb2wgPSAiQ2FyX3BlciIsIGNvbC5yZWdpb25zID0gcmV2KGhjbC5jb2xvcnMoOSwgIi1JbmZlcm5vIikpKSAjcGFsZXRlIGluZmVybm8gY29tIDkgY2xhc3NlcywgcmV2ZXJzZSBjb2xvciByYW1wCmBgYAoKIyBDZW50cm9pZHMKCiMjIEdlb21ldHJpYyBjZW50cm9pZHMKCmBgYHtyfQpDRU5UUk9JRFNnZW8gPSBzdF9jZW50cm9pZChUUklQU2dlb19tdW4pCiMgbWFwdmlldyhDRU5UUk9JRFNnZW8pCmBgYAoKCiMjIFdlaWd0aGVkIGNlbnRyb2lkcwoKR2V0IEJHUkkgRGF0YV5bQmFzZSBHZW9ncsOhZmljYSBkZSBSZWZlcmVuY2lhw6fDo28gZGUgSW5mb3JtYcOnw6NvLCBDZW5zb3MgMjAyMV0gZnJvbSBJTkUgd2Vic2l0ZSwgYXQgKsOBcmVhIE1ldHJvcG9saXRhbmEgZGUgTGlzYm9hKiBsZXZlbDogW2h0dHBzOi8vbWFwYXMuaW5lLnB0L2Rvd25sb2FkL2luZGV4MjAyMS5waHRtbF0obWFwYXMuaW5lLnB0L2Rvd25sb2FkL2luZGV4MjAyMS5waHRtbCkKCmBgYHtyIGdldGNlbnN1c30KQkdSSSA9IHN0X3JlYWQoIm9yaWdpbmFsL0JHUkkyMV9MSVNCT0EuZ3BrZyIsIHF1aWV0ID0gVFJVRSkKYGBgCgpJdCBpcyBub3QgdGhhdCBlYXN5IHRvIGVzdGltYXRlIHdlaWdodGVkIGNlbnRyb2lkcyB3aXRoIFIuIFNlZSBbaGVyZV0oaHR0cHM6Ly93emJzb2NpYWxzY2llbmNlY2VudGVyLmdpdGh1Yi5pby9zcGF0aWFsbHlfd2VpZ2h0ZWRfYXZnLykuICAKV2Ugd2lsbCBtYWtlIGEgYnJpZGdlIGNvbm5lY3Rpb24gdG8gUUdJUyB0byB1c2UgaXRzIG5hdGl2ZSBmdW5jdGlvbiBvZiBtZWFuIGNvb3JkaW5hdGVzLiAgCgoKYGBge3J9CmxpYnJhcnkocWdpc3Byb2Nlc3MpCiMgcWdpc19zZWFyY2hfYWxnb3JpdGhtcygibWVhbiIpICMgc2VhcmNoIHRoZSBleGFjdCBmdW5jdGlvbiBuYW1lCiMgcWdpc19nZXRfYXJndW1lbnRfc3BlY3MoIm5hdGl2ZTptZWFuY29vcmRpbmF0ZXMiKSB8PiBzZWxlY3QobmFtZSwgZGVzY3JpcHRpb24pICMgc2VlIHRoZSByZXF1aXJlZCBpbnB1dHMKCiMgd2l0aCBwb3B1bGF0aW9uCkNFTlRST0lEU3BvcCA9IHFnaXNfcnVuX2FsZ29yaXRobShhbGdvcml0aG0gPSAibmF0aXZlOm1lYW5jb29yZGluYXRlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTlBVVCA9IEJHUkksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXRUlHSFQgPSAiTl9JTkRJVklEVU9TIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFVJRCA9ICJEVE1OMjEiKQpDRU5UUk9JRFNwb3AgPSBzdF9hc19zZihDRU5UUk9JRFNwb3ApCgojIHdpdGggYnVpbGRpbmdzCkNFTlRST0lEU2J1aWxkID0gcWdpc19ydW5fYWxnb3JpdGhtKGFsZ29yaXRobSA9ICJuYXRpdmU6bWVhbmNvb3JkaW5hdGVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElOUFVUID0gQkdSSSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFdFSUdIVCA9ICJOX0VESUZJQ0lPU19DTEFTU0lDT1MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVUlEID0gIkRUTU4yMSIpCkNFTlRST0lEU2J1aWxkID0gc3RfYXNfc2YoQ0VOVFJPSURTYnVpbGQpCmBgYAoKCiMjIENvbXBhcmUgaW4gbWFwCgpgYGB7ciBtYXBjZW50cm9pZH0KbWFwdmlldyhDRU5UUk9JRFNnZW8pICsgbWFwdmlldyhDRU5UUk9JRFNwb3AsIGNvbC5yZWdpb24gPSAicmVkIikgKyBtYXB2aWV3KENFTlRST0lEU2J1aWxkLCBjb2wucmVnaW9uID0gImJsYWNrIikKYGBgCgpTZWUgaG93IHRoZSBidWlsZGluZywgcG91bGF0aW9uIGFuZCBnZW9tZXRyaWMgY2VudHJvaWRzIG9mIE1vbnRpam8gYXJlIGFwcGFydCwgZnJvbSBjbG9zZXIgdG8gVGFndXMsIHRvIHRoZSBydXJhbCBhcmVhLgoKCiMgRGVzaXJlIExpbmVzCgpEb3dubG9hZCBgVFJJUFNkbF9tdW4uZ3BrZ2AgCgpgYGB7ciBnZXRkbH0KVFJJUFNkbF9tdW4gPSBzdF9yZWFkKCJnZW8vVFJJUFNkbF9tdW4uZ3BrZyIsIHF1aWV0ID0gVFJVRSkgCmBgYAoKRmlsdGVyIGludHJhem9uYWwgdHJpcHMsIGFuZCB0cmlwcyB3aXRoIG9yaWdpbiBvciBkZXNpbmF0aW9uIGluIExpc2Jvbi4KCmBgYHtyIHdpdGhseH0KVFJJUFNkbF9tdW4gPSBUUklQU2RsX211biB8PiAKICBmaWx0ZXIoT3JpZ2luX211biAhPSBEZXN0aW5hdGlvbl9tdW4pIHw+IAogIGZpbHRlcihUb3RhbCA+IDUwMDApICMgcmVtb3ZlIG5vaXNlCgptYXB2aWV3KFRSSVBTZGxfbXVuLCB6Y29sID0gIlRvdGFsIiwgbHdkID0gNSkKYGBgCgpgYGB7ciBmaWx0ZXJseH0KVFJJUFNkbF9tdW5fbm9MWCA9IFRSSVBTZGxfbXVuIHw+IAogIGZpbHRlcihPcmlnaW5fbXVuICE9ICJMaXNib2EiLCBEZXN0aW5hdGlvbl9tdW4gIT0gIkxpc2JvYSIpCgptYXB2aWV3KFRSSVBTZGxfbXVuX25vTFgsIHpjb2wgPSAiVG90YWwiLCBsd2QgPSA4KQpgYGAKCllvdSBjYW4gcmVwbGFjZSB0aGUgYFRvdGFsYCB3aXRoIG90aGVyIHZhcmlhYmxlLCBzdWNoIGFzIGBDYXJgLCBgUFRyYW5zaXRgLCBhbmQgc28gb24uCgo+IE5vdGU6IFRoZSBmdW5jdGlvbiBbYG9kX29uZXdheSgpYF0oaHR0cHM6Ly9kb2NzLnJvcGVuc2NpLm9yZy9zdHBsYW5yL3JlZmVyZW5jZS9vZF9vbmV3YXkuaHRtbCkgYWdncmVnYXRlcyBvbmV3YXkgbGluZXMgdG8gcHJvZHVjZSBiaWRpcmVjdGlvbmFsIGZsb3dzLiBCeSBkZWZhdWx0LCBpdCByZXR1cm5zIHRoZSBzdW0gb2YgZWFjaCBudW1lcmljIGNvbHVtbiBmb3IgZWFjaCBiaWRpcmVjdGlvbmFsIG9yaWdpbi1kZXN0aW5hdGlvbiBwYWlyLiBUaGlzIGlzIGJldHRlciBmb3Igdml6IHB1cnBvdXNlcy4KCiMgRXVjbGlkZWFuIHZzLiBSb3V0aW5nIGRpc3RhbmNlCgojIyBFdWNsaWRlYW4gZGlzdGFuY2UKCiMjIyBDcmVhdGUgbmV3IHBvaW50IGF0IElTVAoKYGBge3IgY3JlYXRlaXN0fQpJU1QgPSBzdF9zZmMoc3RfcG9pbnQoYygtOS4xMzk3NDA0LCAzOC43MzcwMTY4KSksIGNycyA9IDQzMjYpCmBgYAoKIyMjIEltcG9ydCBzdXJ2ZXkgYW5kIHZpc3VhbGl6ZQoKYGBge3Igc3VydmV5fQpTVVJWRVkgPSByZWFkLmNzdigiZ2VvL1NVUlZFWWlzdC50eHQiLCBzZXAgPSAiXHQiKSAjIHRhYiBkZWxpbWl0ZXIKU1VSVkVZID0gc3RfYXNfc2YoU1VSVkVZLCBjb29yZHMgPSBjKCJsb24iLCAibGF0IiksIGNycyA9IDQzMjYpICMgdHJhbnNmb3JtIGFzIGdlbyBkYXRhCgptYXB2aWV3KFNVUlZFWSwgemNvbCA9ICJNT0RFIikgKyBtYXB2aWV3KElTVCwgY29sLnJlZ2lvbiA9ICJyZWQiLCBjZXggPSAxMikKYGBgCgojIyMgUmVwcm9qZWN0IGxheWVycwoKSW4gUiB3ZSBjYW4gcHJvY2VzcyBkaXN0YW5jZXMgaW4gbWV0ZXJzIG9uLWZseS4KCkJ1eSBoZXJlIGlzIHRoZSBjb2RlIHRvIHByb2plY3QgbGF5ZXJzIGZyb20gR2VvZ3JhcGhpYyBjb29yZGluYXRlcyAoV0dTIDg0IC0gRVBTRzpbNDM2NF0oaHR0cHM6Ly9lcHNnLmlvLzQzMjYpKSB0byBQcm9qZWN0ZWQgY29vcmRpbmF0ZXMgKFBzZXVkby1NZXJjYXRvciAtIEVQU0c6WzM4NTddKGh0dHBzOi8vZXBzZy5pby8zODU3KSwgb3IgUG9ydHVndWVzZSBUcmFudmVyc29yLU1lcmNhdG9yIDA2IC0gRVBTRzpbMzc2M10oaHR0cHM6Ly9lcHNnLmlvLzM3NjMpKS4KCmBgYHtyIHByb2plY3RsYXllcnN9CklTVHByb2plY3RlZCA9IHN0X3RyYW5zZm9ybShJU1QsIGNycyA9IDM4NTcpClNVUlZFWXByb2plY3RlZCA9IHN0X3RyYW5zZm9ybShTVVJWRVksIGNycyA9IDM4NTcpCmBgYAoKIyMjIFN0cmFpZ2h0IGxpbmVzIGFuZCBkaXN0YW5jZQoKTmVhcmVzdCBwb2ludCBiZXR3ZWVuIHRoZSB0d28gbGF5ZXJzLiBBcyB3ZSBvbmx5IGhhdmUgMSBwb2ludCBhdCBJU1QgbGF5ZXIsIHdlIHdpbGwgaGF2ZSB0aGUgc2FtZSBudW1iZXIgb2YgbGluZXMgYXMgbnVtYmVyIG9mIHN1cnZleXMgPSBgciBucm93KFNVUlZFWSlgLgoKYGBge3IgZXVjZGlzdGFuY2V9ClNVUlZFWWV1Y2xpZGVhbiA9IHN0X25lYXJlc3RfcG9pbnRzKFNVUlZFWSwgSVNULCBwYWlyd2lzZSA9IFRSVUUpIHw+IHN0X2FzX3NmKCkgIyB0aGlzIGNyZWF0ZXMgbGluZXMKCm1hcHZpZXcoU1VSVkVZZXVjbGlkZWFuKQoKU1VSVkVZJGRpc3RhbmNlID0gc3RfbGVuZ3RoKFNVUlZFWWV1Y2xpZGVhbikgIyBjb21wdXRlIGRpc3RhbmNlIGFuZCBhZGQgZGlyZWN0bHkgaW4gdGhlIGZpcnN0IGxheWVyCgpzdW1tYXJ5KFNVUlZFWSRkaXN0YW5jZSkgIyBpbiBtZXRlcnMKYGBgCgpUaGUgc2FtZSBmdW5jdGlvbiBjYW4gYmUgdXNlZCB0byBmaW5kIHRoZSBjbG9zZXN0IEdJUkEgc3RhdGlvbiB0byBlYWNoIHN1cnZleSBob21lIGxvY2F0aW9uLiBBbmQgYWxzbyBjaGVjayB3aGVyZSBhcmUgdGhlIG9uZXMgdGhhdCBhcmUgZmFyIGF3YXkgZnJvbSBHSVJBLgoKYGBge3J9CkdJUkEgPSBzdF9yZWFkKCJnZW8vR0lSQTIwMjMuZ2VvanNvbiIsIHF1aWV0ID0gVFJVRSkgIyB3ZSBjYW4gYWxzbyByZWFkIGdlb2pzb24gd2l0aCB0aGlzIGZ1bmN0aW9uIQoKbmVhcmVzdCA9IHN0X25lYXJlc3RfZmVhdHVyZShTVVJWRVksIEdJUkEpICMgY3JlYXRlcyBhbiBpbmRleCBvZiB0aGUgY2xvc2VzdCBHSVJBIHN0YXRpb24gaWQKClNVUlZFWSRkaXN0YW5jZUdJUkEgPSBzdF9kaXN0YW5jZShTVVJWRVksIEdJUkFbbmVhcmVzdCxdLCBieV9lbGVtZW50ID0gVFJVRSkKCm1hcHZpZXcoU1VSVkVZLCB6Y29sID0gImRpc3RhbmNlR0lSQSIpICsKICBtYXB2aWV3KEdJUkEsIGNvbC5yZWdpb25zID0gImdyZXkyMCIsIGNleCA9IDQsIGxlZ2VuZCA9IEZBTFNFKQpgYGAKCiMjIFJvdXRpbmcgZGlzdGFuY2UKClVzaW5nIGFuIEFQSSBrZXkgZnJvbSBbT3BlblJvdXRlU2VydmljZV0oaHR0cHM6Ly9vcGVucm91dGVzZXJ2aWNlLm9yZy9kZXYvIy9zaWdudXApLCB5b3Ugc2hvdWxkIHN0b3JlIGl0IGF0IHlvdXIgY29tcHV0ZXIgYW5kICoqbmV2ZXIgc2hvdyBpdCBkaXJlY3RseSBvbiBjb2RlKiouICAKRm9yIHRoYXQgYHVzZXRoaXM6OmVkaXRfcl9lbnZpcm9uKClgIGFuZCBwYXN0ZSB5b3VyIHRva2VuIGFzIGBPUlNfQVBJX0tFWT0ieHh4eHh4eHh4eHh4eHh4eHh4eHh4eCJgIChyZXBsYWNlIHdpdGggeW91ciB0b2tlbikuIFNhdmUgdGhlIC5SZW52aXJvbiBmaWxlLCBhbmQgcHJlc3MgYEN0cmwrU2hpZnQrRjEwYCB0byByZXN0YXJ0IFIgc28gaXQgY2FuIHRha2UgZWZmZWN0LgoKPCEtLSBVc2UgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJHSVNjaWVuY2Uvb3BlbnJvdXRlc2VydmljZS1yIikgLS0+CgoqV29yayBJbiBQcm9ncmVzcyo=